package com.imooc.netty.samples.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ClientDemo {
    /*------------------ 私有常量 ------------------*/
     private static final String HOST = "127.0.0.1";
     private static final int PORT = 10086;
//    private static final String HOST = "101.34.234.196";
//    private static final int PORT = 8080;
    private static final int MSG_ALL_COUNT = 100; // 所有线程发送的总消息个数
    private static final int THREAD_COUNT = 10; // 线程数
    // 用于生成指定长度的随机字符串
    private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    // 生成字符串消息的长度
    private static final int MSG_LEN = 10;
    private static final Random RANDOM = new Random();

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
//        sendMsg1(); // 单线程、手动发送消息
//        sendMsg2(); // 单线程、自动发送消息
        sendMsg3();   // 多线程、自动发送消息
        /*
         * 注:客户端使用sendMsg3()多线程时,服务端应该使用multiThreadProcess();
         * 若是服务端使用singleThreadProcess()单线程处理,则客户端的线程中、仅有一个能建立连接发送消息，
         * 其他均报"java.net.SocketException: Connection reset"异常
         */
        long endTime = System.currentTimeMillis();
        double time = 1.0 * (endTime - startTime) / 1000; // 除以1000是毫秒转秒
        double speed = MSG_ALL_COUNT / time;
        System.out.println(String.format("MSG_ALL_COUNT=%s, time=%.4f, speed=%.4f", MSG_ALL_COUNT, time, speed));
    }

    private static void sendMsg1() {
        try {
            Socket clientSocket = new Socket(HOST, PORT);// 创建Socket对象,指定服务端的IP地址和端口号
            System.err.println("connect server successfully, client socket: " + clientSocket);
            // 获取输入流和输出流,输入流和输出流是通过socket对象来进行数据传输的
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));// 从控制台读取用户输入
            String message;
            System.out.println("input 'exit' end.");
            while (true) {
                System.out.print("input: ");
                message = reader.readLine();
                if (message.equalsIgnoreCase("exit")) {
                    out.println("exit");// 如果用户输入'exit',发送终止标志给服务端并退出循环
                    clientSocket.close();
                    System.err.println("disconnect, client socket: " + clientSocket);
                    break;
                }
                out.println(message);// 将用户输入的信息发送给服务端
                String response = in.readLine();// 接收服务端的响应并打印
                System.out.println("server response: " + response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void sendMsg2() {
        try {
            Socket clientSocket = new Socket(HOST, PORT);
            System.err.println("connect server successfully, client socket: " + clientSocket);
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            final int msgCount = MSG_ALL_COUNT; // 单线程发送,该线程发送的消息个数即所有消息个数
            for (int i = 0; i < msgCount; i++) {
                String message = getMsg(i, clientSocket);
                out.println(message);
                String response = in.readLine();
                System.out.println("server response: " + response);
            }
            // 消息发完后,发送exit命令退出
            // TODO:为啥先打印断开连接,再打印的发送消息的内容,异步的(非必现,多试几次即可;跑多线程时尤为明显)?有办法确保消息全部发送后,再打印断开连接吗?
            out.println("exit");
            clientSocket.close();
            System.err.println("disconnect, client socket: " + clientSocket);
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("----------------- 统计信息 -----------------");
        System.out.println("THREAD_COUNT = " + 1); // 此处写固定值1
    }

    private static void sendMsg3() {
        final int threadCount = 10;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < threadCount; i++) {
            executorService.execute(new ClientTask());
        }
        executorService.shutdown();

        // 若是线程池中的线程还没有执行完,则休眠10ms
        while (!executorService.isTerminated()) {
            try {
                Thread.sleep(10L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("----------------- 统计信息 -----------------");
        System.out.println("THREAD_COUNT = " + THREAD_COUNT);
    }

    /**
     * 获取随机生成的字符串消息
     *
     * @return
     */
    private static String getMsg(int i, Socket clientSocket) {
        StringBuilder sb = new StringBuilder();
        sb.append(i).append("_"); // 拼接消息序号
        for (int j = 0; j < MSG_LEN; j++) {
            int index = RANDOM.nextInt(CHARACTERS.length());
            char ch = CHARACTERS.charAt(index);
            sb.append(ch);
        }
        sb.append(", client socket:").append(clientSocket); // 拼接客户端,多线程时用于区别
        return sb.toString();
    }

    private static class ClientTask implements Runnable {
        @Override
        public void run() {
            try {
                Socket clientSocket = new Socket(HOST, PORT);
                System.err.println("connect server successfully, client socket: " + clientSocket);
                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
                final int msgCount = MSG_ALL_COUNT / THREAD_COUNT; // 计算出每个线程发送的消息数
                for (int i = 0; i < msgCount; i++) {
                    String message = getMsg(i, clientSocket);
                    out.println(message);
                    String response = in.readLine();
                    System.out.println("server response: " + response);
                }
                out.println("exit");
                clientSocket.close();
                System.err.println("disconnect, client socket: " + clientSocket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
